/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2014-2019.
 * Description: implement the proc fs for kbox.
 *              The basic directory is /proc/kbox
 * Author: lichaoyue
 * Create: 2014-3-24
 */

#include "kbox_include.h"
#include <asm/io.h>
#include <linux/rtos_sec.h>
#include "ddrramdriver.h"
struct semaphore kbox_sem_ddr;
#ifdef CONFIG_RTOS_KBOX_VIRTUAL
#define MALLOC_TAG "malloc"
bool use_vmalloc;
#endif
#include <linux/proc_fs.h>

/*
 * no need to protect proc delete for filesystem will protect file would not delete when open it
 * and before free region control block, we must delete region proc file first. so wen read region, control block
 *  wouldn't be null
 */
static int kbox_proc_open(struct inode *inode, struct file *filp)
{
	kbox_region_cb *region_cb = NULL;
	int ret = 0;
	unsigned long flags;

	if (check_msg_restrict()) {
		error("check permissions fail on open\n");
		return -EPERM;
	}

	region_cb = PDE_DATA(inode);
	if (!region_cb)
		return -EINVAL;

	kbox_printk_lock(&region_cb->logbuf_lock, &flags);
	if (atomic_cmpxchg(&region_cb->proc_read_count, 0, 1) != 0) {
		ret = -EBUSY;
	} else {
		region_cb->fpos = get_desc_log_start(region_cb->pregion_des);
		region_cb->part_copy = 0;
		region_cb->reset_fpos = false;
	}

	kbox_printk_unlock(&region_cb->logbuf_lock, &flags);

	return ret;
}

static int kbox_proc_release(struct inode *inode, struct file *filp)
{
	kbox_region_cb *cb;

	cb = PDE_DATA(inode);
	if (!cb)
		return -EINVAL;

	cb->fpos = 0;
	cb->part_copy = 0;
	cb->reset_fpos = false;
	atomic_set(&cb->proc_read_count, 0);
	return 0;
}

static ssize_t kbox_proc_read(struct file *file,
	char __user *buffer, size_t buflen, loff_t *fpos)
{
	kbox_region_cb *cb = NULL;
	int ret;

	if (check_msg_restrict()) {
		error("check permissions fail on read\n");
		return (ssize_t)-EPERM;
	}

	cb = PDE_DATA(file_inode(file));
	if (!cb) {
		return -EINVAL;
	}

	ret = kbox_region_read(cb, buffer, buflen);

	return ret;
}

static ssize_t kbox_proc_write(struct file *filp, const char __user *buffer, size_t count, loff_t *ppos)
{
	kbox_region_cb *cb = NULL;
	unsigned long flags;

	if (check_syslog_permissions(SYSLOG_ACTION_CLEAR, SYSLOG_FROM_PROC)) {
		error("check_syslog_permissions fail on write\n");
		return (ssize_t)-EPERM;
	}

	if (count != 1) {
		error("invalidate param!\n");
		return (ssize_t)-EINVAL;
	}

	cb = PDE_DATA(file_inode(filp));
	if (!cb) {
		return -EINVAL;
	}

	if (cb->is_history == TRUE) {
		error("this is a readonly history region\n");
		return -EACCES;
	}

	kbox_printk_lock(&cb->logbuf_lock, &flags);
	zero_log_buf(cb, 0, NULL);

	kbox_printk_unlock(&cb->logbuf_lock, &flags);

	return count;
}

/* kbox proc operation sets */
const struct proc_ops kbox_proc_file_operations = {
	.proc_read = kbox_proc_read,
	.proc_write = kbox_proc_write,
	.proc_open = kbox_proc_open,
	.proc_release = kbox_proc_release,
	.proc_lseek = no_llseek,
};

static bool module_exist(const char *name)
{
	struct module *mod = NULL;

	mutex_lock(&module_mutex);
	mod = find_module(name);
	mutex_unlock(&module_mutex);

	if (mod == NULL)
		return false;
	return true;
}

static int erase_all_kbox_regions(void)
{
	struct list_head *ptr = NULL;
	kbox_region_cb *entry = NULL;
	int is_module_loaded = 0;
	int ret = 0;

	list_for_each(ptr, &kbox_region_list) {
		entry = list_entry(ptr, kbox_region_cb, list);
		if (entry->module != NULL && module_exist(entry->module_name)
		    && entry->module != kbox_module) {
			error("%s working, we can not erase messages\n",
			       entry->module_name);
			is_module_loaded = 1;
			ret = -EBUSY;
			break;
		}
	}
	if (!is_module_loaded) {
		kbox_dev_cb.dev_status = KBOX_DEV_STATUS_FAULT;
		kbox_erase_regions();
		ret = kbox_create_default_reg();
		if (ret < 0)
			error("create default region fail!\n");
		kbox_dev_cb.dev_status = KBOX_DEV_STATUS_FS_OK;
	}

	return ret;
}

static ssize_t kbox_proc_erase_write(struct file *filp, const char __user *buffer, size_t count, loff_t *ppos)
{
	int ret = 0;
	char flag[2];

	if (check_syslog_permissions(SYSLOG_ACTION_CLEAR, SYSLOG_FROM_PROC)) {
		error("check_syslog_permissions fail on write\n");
		return (ssize_t)-EPERM;
	}

	if (kbox_dev_cb.dev_status != KBOX_DEV_STATUS_FS_OK) {
		error("You should insert device first~\n");
		return -ENODEV;
	}
	/* we just accept "1" for parameter */
	if (count != 2) {
		error("invalid parameter\n");
		return -EINVAL;
	}

	if (down_interruptible(&kbox_sem) < 0) {
		/* -EINTR */
		return -EINTR;
	}

	if (copy_from_user(flag, buffer, 2)) {
		error("copy data fail or no data\n");
		ret = -EIO;
		goto out;
	}

	if (flag[0] == '1') {
		ret = erase_all_kbox_regions();
	} else {
		error("invild parameter, input \'1\' to erase kbox messages\n");
		ret = -EINVAL;
	}
out:
	up(&kbox_sem);
	return ret < 0 ? ret : count;
}

static int kbox_proc_erase_open(struct inode *inode, struct file *filp)
{
	if (check_syslog_permissions(SYSLOG_ACTION_OPEN, SYSLOG_FROM_PROC)) {
		error("check_syslog_permissions fail on open\n");
		return -EPERM;
	}
	return 0; /* success */
}

static int kbox_proc_earse_release(struct inode *inode, struct file *filp)
{
	return 0;
}

static const struct proc_ops erase_ops = {
	.proc_write = kbox_proc_erase_write,
	.proc_open = kbox_proc_erase_open,
	.proc_release = kbox_proc_earse_release,
};

#ifndef CONFIG_RTOS_KBOX_MINI
static void *ct_seq_start(struct seq_file *s, loff_t *pos)
{
	return *pos < 1 ? (void *)1 : NULL;
}

static void *ct_seq_next(struct seq_file *s, void *v, loff_t *pos)
{
	(*pos)++;
	return NULL;
}

static void ct_seq_stop(struct seq_file *s, void *v)
{
	return;
}

#define BYTE_TO_KB	10

static int ct_seq_show(struct seq_file *s, void *v)
{
	struct list_head *ptr = NULL;
	kbox_region_cb *entry = NULL;
	int ret;

	seq_printf(s, "device name:%s\n"
		"*********************************************************************\n"
		"%-20s%12s%12s%12s%12s\n",
		kbox_dev_cb.dev_desc->device_name,
		"region",
		"start",
		"regionsize",
		"datasize",
		"permission");

	ret = down_interruptible(&kbox_sem);
	if (ret < 0)
		return ret;

	list_for_each(ptr, &kbox_region_list) {
		entry = list_entry(ptr, kbox_region_cb, list);
		seq_printf(s, "%-20s%12u%12u%12u",
			entry->name,
			get_desc_offset(entry->pregion_des),
			get_desc_size(entry->pregion_des),
			get_desc_real_size(entry->pregion_des));
		if (entry->is_history == TRUE)
			seq_printf(s, "%12s\n", "readonly");
		else
			seq_printf(s, "%12s\n", "read-write");
	}

	seq_printf(s,
		"*********************************************************************\n"
		"total:%ukb    used:%ukb    free:%ukb\n",
		(uint)(kbox_dev_cb.dev_desc->size >> BYTE_TO_KB),
		(uint)(kbox_memory_list.fs_head->basic.used_size >> BYTE_TO_KB),
		(uint)((kbox_dev_cb.dev_desc->size - kbox_memory_list.fs_head->basic.used_size) >> BYTE_TO_KB));

	up(&kbox_sem);
	return 0;
}

static const struct seq_operations ct_seq_ops = {
	.start = ct_seq_start,
	.next = ct_seq_next,
	.stop = ct_seq_stop,
	.show = ct_seq_show
};

static int kbox_proc_devinfo_open(struct inode *inode, struct file *filp)
{
	struct kbox_dev_des *dev_des = NULL;

	if (check_msg_restrict()) {
		error("check permissions fail on open\n");
		return -EPERM;
	}

	if (down_interruptible(&kbox_sem) < 0)
		return -EINTR;

	dev_des = kbox_dev_cb.dev_desc;

	up(&kbox_sem);
	if (dev_des == NULL) {
		error("there is no device now\n");
		return -ENODEV;
	}

	return seq_open(filp, &ct_seq_ops);  /* success */
}

static const struct proc_ops devinfo_ops = {
	.proc_read = seq_read,
	.proc_open = kbox_proc_devinfo_open,
	.proc_release = seq_release,
	.proc_lseek = seq_lseek,
};
#endif

#ifdef CONFIG_RTOS_KBOX_VIRTUAL
static int init_kbox_mem_by_vmalloc(phys_addr_t size)
{
	struct kbox_dev_des *cb = &st_ddr_ram_device;
	int ret;
	char *ddr_mmap_addr = vmalloc(size);

	if (ddr_mmap_addr == NULL) {
		kbox_debug_trace();
		error("vmalloc kbox ddram_size=%llx fail\n",
				(unsigned long long)size);
		return -EADDRNOTAVAIL;
	}
	kbox_kbox_dev_des_init(0, size, ddr_mmap_addr);
	ret = kbox_register_device(cb);
	if (ret < 0) {
		vfree(ddr_mmap_addr);
		cb->map_op.mapped_addr = NULL;
		error("DDR Ram driver registration to kbox failed\n");
		return ret;
	}
	use_vmalloc = true;
	return ret;
}
#endif

static int init_kbox_mem(phys_addr_t addr, phys_addr_t size, bool is_use_vmalloc)
{
	struct kbox_dev_des *cb = &st_ddr_ram_device;
	int ret;

	if (down_interruptible(&kbox_sem_ddr) < 0)
		return -EINTR;

	if (cb->map_op.mapped_addr != NULL) {
		error("You already registered kbox mem\n");
		ret = -EBUSY;
		goto out;
	}
#ifdef CONFIG_RTOS_KBOX_REGION_COMPAT
	/* must set kbox_compat_value before kbox mem init */
	if (kbox_compat_enable)
		kbox_compat_value = KBOX_COMPAT_ADJUST_SIZE;
#endif

#ifndef CONFIG_RTOS_KBOX_VIRTUAL
	ret = init_kbox_mem_by_ioremap(addr, size);
#else
	if (is_use_vmalloc)
		ret = init_kbox_mem_by_vmalloc(size);
	else
		ret = init_kbox_mem_by_ioremap(addr, size);
#endif
out:
	up(&kbox_sem_ddr);
	return ret;
}

static ssize_t kbox_proc_mem_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
	int ret;
	char buffer[64] = {0}; /* for address and size 64byte is enough */
	char *tmp = NULL;
	phys_addr_t value[2] = {0};
	bool tmp_use_vmalloc = false;

	if (check_syslog_permissions(SYSLOG_ACTION_CLEAR, SYSLOG_FROM_PROC)) {
		error("check_syslog_permissions fail on write\n");
		return (ssize_t)-EPERM;
	}
	if (count >= (sizeof(buffer) - 1))
		return -EINVAL;

	if (copy_from_user(buffer, buf, count))
		return -EFAULT;

#ifndef CONFIG_RTOS_KBOX_VIRTUAL
	value[0] = memparse(buffer, &tmp);
#else
	tmp = strstr(buffer, MALLOC_TAG);
	if (tmp) {
		tmp_use_vmalloc = true;
		tmp = tmp + sizeof(MALLOC_TAG) - 1;
	} else {
		value[0] = memparse(buffer, &tmp);
	}
#endif
	if (*tmp == ' ')
		value[1] = memparse(tmp + 1, NULL);

#ifdef CONFIG_RTOS_KBOX_VIRTUAL
	if (value[1] == 0)
		return -EINVAL;
#endif
	ret = init_kbox_mem(value[0], value[1], tmp_use_vmalloc);
	return ret >= 0 ? count : ret;
}

static ssize_t kbox_proc_mem_read(struct file *file, char __user *buf,
		size_t count, loff_t *ppos)
{
	char buffer[64] = {0};

	if (check_syslog_permissions(SYSLOG_ACTION_SIZE_BUFFER, SYSLOG_FROM_PROC)) {
		error("check_syslog_permissions fail on read\n");
		return (ssize_t)-EPERM;
	}
	/* kbox addr can not be set to 0 */
	if (kbox_dev_cb.dev_desc == NULL || kbox_dev_cb.dev_desc->map_op.mapped_addr == NULL)
		return -ENODEV;

	/*
	 * Converting 64-bit numbers to hexadecimal takes 16 bits,
	 * so 2+16+1+2+16+1+1=40, less than 64.
	 */
#ifndef CONFIG_RTOS_KBOX_VIRTUAL
	snprintf(buffer, sizeof(buffer), "%pa 0x%x\n",
		 &(kbox_dev_cb.dev_desc->map_op.phy), kbox_dev_cb.dev_desc->size);
#else
	if (use_vmalloc)
		snprintf(buffer, sizeof(buffer), "malloc size 0x%x\n",
			 kbox_dev_cb.dev_desc->size);
	else
		snprintf(buffer, sizeof(buffer), "%pa 0x%x\n",
			 &(kbox_dev_cb.dev_desc->map_op.phy),
			 kbox_dev_cb.dev_desc->size);
#endif
	return simple_read_from_buffer(buf, count, ppos, buffer, strlen(buffer));
}

static int kbox_proc_mem_open(struct inode *inode, struct file *filp)
{
	if (check_syslog_permissions(SYSLOG_ACTION_OPEN, SYSLOG_FROM_PROC)) {
		error("check_syslog_permissions fail on open\n");
		return (ssize_t)-EPERM;
	}
	return 0;          /* success */
}

static int kbox_proc_mem_release(struct inode *inode, struct file *filp)
{
	return 0;
}

static const struct proc_ops mem_ops = {
	.proc_write = kbox_proc_mem_write,
	.proc_read = kbox_proc_mem_read,
	.proc_open = kbox_proc_mem_open,
	.proc_release = kbox_proc_mem_release,
};

static struct proc_dir_entry *kbox_proc_root;
struct proc_dir_entry *kbox_proc_regions;
/*
 * init  proc fs for kbox
 */
int kbox_proc_init(void)
{
	/*
	 *  create kbox proc fs.
	 *			/proc/kbox
	 *					|--deviceinfo
	 *					|--erasure
	 *					|--mem
	 *					|--regions
	 *							|--panic
	 *							|--
	 */

	/* create /proc/kbox */
	struct proc_dir_entry *kbox_proc_entry = NULL;

	kbox_proc_root = proc_mkdir(KBOX_PROC_NAME, NULL);
	if (kbox_proc_root == NULL) {
		error("Proc entry creation for KBOX_PROC_NAME failed\n");
		return -1;
	}
#ifndef CONFIG_RTOS_KBOX_MINI
	kbox_proc_entry = proc_create_data(KBOX_PROC_NAME_DEVINFO, 0444,
					   kbox_proc_root, &devinfo_ops, NULL);
	if (kbox_proc_entry == NULL) {
		error("Proc entry creation for KBOX_PROC_NAME_DEVINFO failed\n");
		goto failed_create;
	}
#endif

	kbox_proc_entry = proc_create_data(KBOX_PROC_NAME_EREASE, 0200,
					   kbox_proc_root, &erase_ops, NULL);
	if (kbox_proc_entry == NULL) {
		error("Proc entry creation for KBOX_PROC_NAME_EREASE failed\n");
		goto failed_create;
	}

	kbox_proc_entry = proc_create_data(KBOX_PROC_NAME_MEM, 0640,
					   kbox_proc_root, &mem_ops, NULL);
	if (kbox_proc_entry == NULL) {
		error("Proc entry creation for KBOX_PROC_NAME_MEM failed\n");
		goto failed_create;
	}
	sema_init(&kbox_sem_ddr, 1);

	kbox_proc_regions = proc_mkdir(KBOX_PROC_NAME_REGION, kbox_proc_root);
	if (kbox_proc_regions == NULL) {
		error("Proc entry creation for KBOX_PROC_NAME_REGION failed\n");
		goto failed_create;
	}
	return 0;

failed_create:
	remove_proc_subtree(KBOX_PROC_NAME, NULL);
	return -1;
}

/*
 * cleanup proc fs for kbox.
 */
void kbox_proc_fini(void)
{
	remove_proc_subtree(KBOX_PROC_NAME, NULL);
}
